/*
Copyright (C) 2011 by the PSVN Research Group, University of Alberta
*/

#include <stdlib.h>
#include <stdio.h>
#include <inttypes.h>
#include <sys/time.h>
#include <dlfcn.h>
#include <assert.h>
#include "psvn_game_so.h"
#include "hida.hpp"
#include "timer.hpp"

using namespace std;

const bool PRINT_SOLUTIONS = false;

// Uncomment this to parse 15-puzzle instances in wheeler format
//#define PARSE_WHEELER_SLIDE15_DATA

char* print_bytes(size_t bytes)
{
    static char str[128];
    if (bytes < 1024)
        sprintf(str, "%lub", bytes);
    else if (bytes < 1024*1024)
        sprintf(str, "%lukb", bytes / 1024);
    else if (bytes < 1024*1024*1024)
        sprintf(str, "%lumb", bytes / (1024*1024));
    else
        sprintf(str, "%.2lfgb", (double)bytes / (1024*1024*1024));
    return str;
}

int main(int argc, char** argv)
{
    bool use_fringe = false;
    bool clear_cache_between_instances = true;
    string nameFile("-");
    for (int i = 1; i < argc; ++i) {
        if (!strcmp(argv[i], "--use-fringe")) {
            printf("Using fringe search.\n");
            use_fringe = true;
        } else if (!strcmp(argv[i], "--keep-cache")) {
            printf("Retaining cache between instances.\n");
            clear_cache_between_instances = false;
        } else {
            nameFile = string(argv[i]);
        }
    }

    init_dyn_abstractions();

    while (!feof(stdin))
    {
        std::vector<abstraction_t*> abst;
        std::vector<state_t> states;
#ifdef PARSE_WHEELER_SLIDE15_DATA
        if (!ParseWheelerProblemInstance(abst, states))
            break;
#else
        if (!ParseDynamicProblemInstances(abst, states))
            break;
#endif
        printf("==================================\n");        
        printf("Using %lu levels of abstraction.\n", abst.size() - 1);
        DynamicAbstHierarchy ah(abst);
        HIDA<DynamicAbstHierarchy> hida(use_fringe, ah);
        
        printf("Parsed %lu problem%s.\n", states.size(), 
               (states.size() == 1 ? "" : "s"));
        size_t total_expanded = 0;
        size_t total_wasted = 0;
        size_t total_stored = 0;
        vector<size_t> expanded(abst.size(), 0);
        Timer total_timer;
        for (size_t i = 0; i < states.size(); ++i) {
            
            printf( "problem %lu: ", i + 1);
            print_state(stdout, &states[i]);
            printf( "\n" );
            
            if (clear_cache_between_instances)
                hida.ClearCache();
            
            Timer instance_timer;
            vector<const void*> solution;
            int d = hida.Solve(&states[i], &solution);
            if (PRINT_SOLUTIONS) {
                if (solution.empty())
                    printf("no solution\n");
                else {
                    printf("d = %d\n", d);
                    printf("solution (%d moves):\n", (int)solution.size() - 1);
                    for (size_t i = 0; i < solution.size(); ++i) {
                        printf("  ");
                        print_state(stdout, (const state_t*)solution[i]);
                        printf("\n");
                    }
                }
            }
            size_t total = 0;
            vector<size_t> data;

            printf("elapsed  %0.3lf\n", instance_timer.Elapsed());
            
            total = 0;
            hida.NumSearches(data);
            printf("searches");
            for (size_t i = 0; i < data.size(); ++i) {
                printf(" %lu", data[i]);
                total += data[i];
            }
            printf(": %lu\n", total);
            
            hida.NumExpanded(data);
            total = 0;
            printf("expanded");
            for (size_t i = 0; i < data.size(); ++i) {
                printf(" %lu", data[i]);
                total += data[i];
                expanded[i] += data[i];
            }
            printf(": %lu\n", total);
            total_expanded += total;
            
            total = 0;
            hida.NumWasted(data);
            printf("wasted  ");
            for (size_t i = 0; i < data.size(); ++i) {
                printf(" %lu", data[i]);
                total += data[i];
            }
            printf(": %lu\n", total);
            total_wasted += total;
            
            hida.NumStored(data);
            total = 0;
            printf("stored  ");
            for (size_t i = 0; i < data.size(); ++i) {
                printf(" %lu", data[i]);
                total += data[i];
            }
            printf(": %lu\n", total);
            total_stored += total;
            
            hida.Memory(data);
            total = 0;
            printf("memory  ");
            for (size_t i = 0; i < data.size(); ++i) {
                printf(" %s", print_bytes(data[i]));
                total += data[i];
            }
            printf(": %s\n", print_bytes(total));
        }
        double elapsed = total_timer.Elapsed();
        printf("----------------------------------\n");
        printf("pct.work ");
        for (size_t i = 0; i < expanded.size(); ++i)
            printf("%.1f%% ", expanded[i] * 100.0 / total_expanded);
        printf("\n");
        printf("expanded %lu (%.3gm) %.2fm/s\n", total_expanded,
           (total_expanded / 1000000.0),
               (total_expanded / 1000000.0) / elapsed);
        printf("wasted   %lu (%.3gm) %.2f%%\n", total_wasted,
               (total_wasted / 1000000.0),
               (total_wasted * 100.0) / total_expanded);
        printf("stored   %lu (%.3gm) %.2fm/s\n", total_stored,
               (total_stored / 1000000.0),
               (total_stored / 1000000.0) / elapsed);
        printf("elapsed  %.03lfs\n", elapsed);
    }
    return EXIT_SUCCESS;
}
